Skip to content

fix(dashboard): stop the shell scrolling sideways between 768px and ~890px - #588

Merged
guillermoscript merged 2 commits into
masterfrom
fix/dashboard-shell-overflow-586
Jul 28, 2026
Merged

fix(dashboard): stop the shell scrolling sideways between 768px and ~890px#588
guillermoscript merged 2 commits into
masterfrom
fix/dashboard-shell-overflow-586

Conversation

@guillermoscript

Copy link
Copy Markdown
Owner

What

Adds min-w-0 to SidebarInset and moves the student gamification header card from the md breakpoint to lg (in lockstep with its avatar-dropdown fallback), so no /dashboard/* page scrolls horizontally any more.

Closes #586

Why

Between 768px and roughly 890px, every dashboard page could only be read by panning sideways — the right edge of the content was simply unreachable. Two independent defects combined to cause it, and both are fixed here.

1. SidebarInset could not shrink. components/ui/sidebar.tsx had it as relative flex w-full flex-1 flex-col … with no min-w-0. As a flex item its min-width resolves to auto, so it refuses to go below its min-content width and widens the whole document rather than letting the offending child clip or scroll inside itself. This is a latent trap for any wide descendant — tables, code blocks, charts — not only for the case found here, and it applies to /platform/* too since that shell uses the same component.

2. The gamification card turned on at the same breakpoint as the sidebar. The desktop sidebar appears at md and claims 256px; the card was gated on hidden md:block, so it arrived at the exact moment its column got 256px narrower. Its intrinsic width (a shrink-0 Level block plus a fixed w-20/w-24 XP bar) plus the locale, theme and avatar controls then exceeded what was left.

Measuring teacher and admin dashboards is what isolated the second cause. They render the identical shell, with the identical 256px sidebar, and never overflowed at any width — the only difference is that they have no gamification card:

page 700 768 800 850 900 1024 1280
/dashboard/student 0 87 55 5 0 0 0
/dashboard/student/courses 0 92 60 0 0 0 0
/dashboard/student/browse 0 87 55 5 0 0 0
/dashboard/teacher 0 0 0 0 0 0 0
/dashboard/admin 0 0 0 0 0 0 0

(px of documentElement.scrollWidth - clientWidth, on unmodified master. Every cell is 0 after this change.)

Fixing only the first leaves the card cramped into a column too narrow for it; fixing only the second leaves the shell fragile for the next wide thing anyone adds. Hence both.

The trade worth reviewing

Between 768px and 1023px the streak/coins/level chips are no longer always visible in the header. They move to the avatar dropdown, which already carried them as the small-screen fallback — its gate moved from md:hidden to lg:hidden in the same commit. Those two breakpoints are now a matched pair, and if they ever drift apart the chips would silently vanish (or double up) in the gap, so there is a test asserting the card is visible in exactly one of the two places at every width.

Losing glanceable chips in a 255px-wide band is, I think, clearly better than a page that cannot be read without panning — but it is a product decision, so flag it if you disagree. The alternative considered was letting the chips wrap or truncate in place; that keeps them visible but makes the header two rows tall in the same band, which felt worse.

Also matched the card's loading skeleton to its loaded state (max-w-full overflow-hidden) — the skeleton was briefly wider than the thing it stood in for.

How to QA

On a fresh npm run db:reset, with npm run dev running. Account: student@e2etest.com / password123 on default.lvh.me:3000.

  1. Log in and land on /en/dashboard/student.
  2. Resize the browser slowly from ~1024px down to ~700px. No horizontal scrollbar should ever appear, and the page should never need panning to reach the right edge. (On master the sideways scroll shows up as soon as you cross below ~890px and is worst at exactly 768px.)
  3. At any width between 768px and 1023px, the level/streak/coins chips should be absent from the header — open the avatar menu in the top right and confirm they are there instead.
  4. At 1024px and above, the chips should be back in the header and not duplicated in the dropdown.
  5. Below 768px (mobile), behaviour is unchanged: sidebar goes off-canvas, chips live in the dropdown.
  6. Repeat step 2 on /en/dashboard/teacher and /en/dashboard/admin (owner@e2etest.com) to confirm nothing regressed on the shells that were already fine.

Automated, and the reason this is worth running rather than just reading:

npm run dev                       # separate terminal
npx playwright test tests/playwright/specs/overflow-586.spec.ts \
  --project=desktop-chromium --workers=1

tests/playwright/specs/overflow-586.spec.ts walks student, teacher and admin dashboards across 700/768/800/850/900/1024/1280 and asserts scrollWidth === clientWidth at every combination, plus the header/dropdown lockstep invariant. It fails on the parent commit with exactly the numbers in the table above — it is a regression test, not a smoke test. (Note: if your .env.local sets E2E_BASE_URL to a non-3000 port, override it to match your dev server.)

Screenshots / GIF

Viewport swept from 700px to 1024px on /dashboard/student.

Before — the layout jumps and the page scrolls sideways across the 768–890px band:

before

After — the same sweep, no horizontal scroll at any point; the tail of the clip opens the avatar dropdown at 820px to show the chips are still reachable there:

after

Stills at 768px, /dashboard/student — before, and the same page scrolled fully right to show what was unreachable, then after:

before before, scrolled fully right after

/dashboard/student/courses at 768px, and /dashboard/student at 820px:

courses, before courses, after dashboard 820, before dashboard 820, after

Checklist

  • npm run typecheck and npm run test:unit pass — typecheck clean, 675 unit tests across 54 files pass
  • npm run build passes
  • Every new tenant-scoped query filters by tenant_id (or the table genuinely has no such column) — no queries added; this is CSS-class and layout-gate only
  • Tested with every relevant role (student / teacher / admin) — all three are measured across seven widths by the new spec
  • Loading and error states handled — the card's loading skeleton was part of the problem and is fixed alongside
  • New UI strings added to both messages/en.json and messages/es.json — n/a, no new strings
  • Migration (if any) applies cleanly on npm run db:reset, has RLS policies, and lib/database.types.ts was regenerated — n/a, no migration

One thing to be aware of when reviewing

min-w-0 on SidebarInset is global — it changes every dashboard and platform page, not just the ones in the table. The failure mode it introduces is the opposite one: content that used to widen the page will now clip or scroll within the inset instead. That is the correct behaviour, and the seven-width sweep across five pages is there to catch anything that reflows badly, but it is a shared shell component and a wider eye on it would be welcome.

guillermoscript and others added 2 commits July 28, 2026 21:06
…890px

Every `/dashboard/*` page could only be read by panning horizontally at
viewports between 768px and roughly 890px. Measured on the student
dashboard, the document was 87px wider than the viewport at 768px, 55px at
800px and 5px at 850px.

Two things collided, and both needed fixing.

`SidebarInset` was `w-full flex-1` with no `min-w-0`. As a flex item its
min-width resolved to `auto`, so it refused to shrink below its min-content
width and widened the document instead of letting the offending content clip
or scroll inside itself. That is a latent trap for any wide child — tables,
code blocks, charts — not only for the case found here.

The content that tripped it was the gamification header card, gated on
`hidden md:block`. The desktop sidebar also appears at `md` and takes 256px,
so the card arrived at exactly the moment the column it lives in got 256px
narrower. Teacher and admin dashboards, which render the same shell without
that card, never overflowed at any width — which is what isolated it.

The card now waits until `lg`. Between 768px and 1023px it is still
reachable in the avatar dropdown, whose gate moved from `md:hidden` to
`lg:hidden` in lockstep; the two breakpoints are a pair, and a test now
asserts the card is visible in exactly one of the two places at every width
rather than in neither. Losing the always-visible chips in that band is a
deliberate trade against a page that cannot be read without panning.

Also matched the card's loading skeleton to its loaded state
(`max-w-full overflow-hidden`), since the skeleton was briefly wider than the
thing it stood in for.

`tests/playwright/specs/overflow-586.spec.ts` walks student, teacher and
admin dashboards across 700/768/800/850/900/1024/1280 and asserts
`scrollWidth === clientWidth` everywhere. It fails on the parent commit with
exactly the numbers above.

Closes #586

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01183C2p5ed7youHQAUdPfR1
Screenshots at 768px and 820px on the student dashboard and courses list,
plus a scrolled-fully-right frame showing what was unreachable before, and a
pair of GIFs sweeping the viewport from 700px to 1024px.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01183C2p5ed7youHQAUdPfR1
@guillermoscript guillermoscript self-assigned this Jul 28, 2026
@guillermoscript guillermoscript added bug Something isn't working fix a fix for some bug labels Jul 28, 2026
@guillermoscript
guillermoscript marked this pull request as ready for review July 28, 2026 19:10
@guillermoscript
guillermoscript merged commit b961579 into master Jul 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working fix a fix for some bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard shell scrolls horizontally between 768px and ~890px

1 participant